GraphQL에서 Date 타입 다루기

GraphQL에서는 Date 타입을 기본으로 제공하지 않기 때문에, 서버에서 Date 값을 받아도 실제로는 string으로 처리되는 문제가 발생합니다. 특히 TypeScript 환경에서는 이로 인해 타입 혼란과 런타임 오류가 발생할 수 있습니다. 이 문제를 깔끔하게 해결하는 방법을 소개합니다.

GraphQL에서 Date 타입 문제

GraphQL
scalar Date

GraphQL에는 Date가 내장 타입이 아니므로, 커스텀 스칼라로 정의해야 합니다. 이 때문에 클라이언트와 서버 간에 날짜 값이 string 형태로 전달되고, 타입스크립트에서는 이를 Date 타입으로 간주해 혼란이 생깁니다.

TS
const date = useQuery(GET_USER_BIRTH_DATE);
console.log(typeof date);

타입스크립트 상으로는 Date처럼 보이지만 실제로는 string이며, 그대로 Date 메서드를 사용하면 오류가 발생합니다.

TS
const date = useQuery(GET_USER_BIRTH_DATE);
console.log(new Date(date));

매번 new Date()로 변환하여 사용하면 되는데 이는 번거롭고 비효율적입니다.

GraphQL에서 Date 타입 변환 미들웨어 구현

서버로부터 응답받은 데이터를 자동으로 Date 객체로 변환하는 미들웨어를 구현하면 문제를 해결할 수 있습니다. 이렇게 하면 클라이언트에서는 별도 처리 없이 Date 메서드를 사용할 수 있습니다.

변환이 필요한 쿼리와 필드 선언

먼저, 어떤 쿼리의 어떤 필드를 Date로 변환할지 정의합니다.

TS
const NEEDED_DATE_CONVERT = {
  GetUserBirthDate: ["getUserBirthDate.birthDate"],
  GetComment: ["getComment.info.createdAt", "getComment.info.updatedAt"],
  PaginateUser: ["paginateUser.docs.${index}.birthDate"],
};
 
const QUERY_LIST = Object.keys(NEEDED_DATE_CONVERT);

배열 형태의 응답(docs)은 ${index}를 사용해 모든 인덱스를 처리할 수 있도록 패턴을 정의합니다.

변환 미들웨어 구현

데이터를 평탄화(flatten) 한 후, 지정한 필드를 Date로 변환하고 다시 원래 구조로 복원(unflatten) 합니다. 해당 작업에서는 flat 라이브러리를 사용하기 때문에 설치가 필요합니다.


미들웨어 코드는 간단합니다.

TS
import { flatten, unflatten } from "flat";
 
const convertDateMiddleware = new ApolloLink((operation, forward) => {
  if (DATE_OPERATION_LIST.includes(operation.operationName)) {
    return forward(operation).map((response) => {
      if (response.data) {
        const flattenData: Record<string, any> = flatten(response.data);
        const values = DATE_CONVERT_QUERY[operation.operationName];
 
        values.forEach((pattern) => {
          // 패턴에 ${index}가 포함되어 있으면 처리
          if (pattern.includes("${index}")) {
            // 정규 표현식으로 index 부분이 숫자인 경우 찾기
            const regex = new RegExp(pattern.replace("${index}", "(\\d+)"));
 
            Object.keys(flattenData).forEach((flatKey) => {
              if (regex.test(flatKey) && flattenData[flatKey]) {
                flattenData[flatKey] = new Date(flattenData[flatKey]);
              }
            });
          } else {
            // 일반적인 키 변환
            Object.keys(flattenData).forEach((flatKey) => {
              if (flatKey === pattern && flattenData[flatKey]) {
                flattenData[flatKey] = new Date(flattenData[flatKey]);
              }
            });
          }
        });
 
        const unflattenData = unflatten(flattenData);
 
        if (unflattenData) {
          response.data = unflattenData;
        }
      }
      return response;
    });
  }
 
  return forward(operation);
});

${index}정규 표현식으로 처리해 배열 내 항목까지 자동 변환하도록 구현했습니다.

결론

이 미들웨어를 사용하면, 서버에서 string으로 전달된 날짜 값을 클라이언트에서 자동으로 Date 객체로 변환해 사용할 수 있습니다. 이제 useQueryuseLazyQuery를 사용할 때 별도로 날짜를 파싱하지 않아도 되므로, 코드가 훨씬 깔끔하고 안전해집니다.


더 나은 방법이 있다면 의견을 남겨주세요.